'''
Created on 29/07/2009

@author: Edward Huynh, Tony Allan

Build a package and upload it to google code.
'''

from zipfile import ZipFile
import base64
import hashlib
# this piece of code actually adds the src dir to the PYTHONPATH. Hence if this script is invoked from command line,
# the user does not have to setup the PYTHONPATH. This will resolve import errors if the PYTHONPATH is not set up correctly.
# Hence the order of the imports are IMPORTANT. Make sure that no other import other an 'import sys' goes above the statement below 

from datastore import *
# TODO: add simplified imports for package and runtime.
from package import googlecode_upload
from optparse import OptionParser

# temp directory name
dist_dir = "dist"

METADATA_STARTUP = Uri('package:startup')
METADATA_FILE_NAME_STR = 'package:filename'
METADATA_STARTUP_STR = 'package:startup'
METADATA_MANIFEST_MODULES_STR = 'package:manifest/modules'

XML_FILE_EXTENSION = '.xml'
CONFIGURATION_XML = 'configuration.xml'

def get_modules(manifest):
    '''
    Passed a Manifest Resource object.
    Returns a dictionary of all the modules that are in the manifest, 
    with a value of True if its a startup module.
    '''
    modules_in_package = {}
    if manifest.has_var(METADATA_MANIFEST_MODULES_STR):
        for module in manifest.get_var(METADATA_MANIFEST_MODULES_STR):
            name = module[METADATA_FILE_NAME_STR]
            modules_in_package[name] = module[METADATA_STARTUP_STR]
    return modules_in_package

def create_resource_from_file(file_name, content, package_name, modules_in_package):
    
    # do not create resource when content is empty. This is because
    # python does not like exec'ing empty content
    if not content or not content.strip():
        return None
    
    # xml is always a serialised resource
    if file_name.endswith(XML_FILE_EXTENSION):
        rs = Resource(serialised_object=content)
    else:
        rs = Resource()
        # encode if not a xml file
        rs.content = base64.b64encode(content)
    
    # set the metadata
    rs.set_metadata(Resource.KEY_URI_PACKAGE_FILENAME, file_name)
    
    if file_name != CONFIGURATION_XML:
        # config file has this data already so need to set it again
        rs.set_metadata(Resource.KEY_URI_PACKAGE_PACKAGENAME, package_name)
        if file_name in modules_in_package:
            if modules_in_package[file_name]:
                rs.set_metadata(METADATA_STARTUP, True)
            else:
                rs.set_metadata(METADATA_STARTUP, False)                   
    
    if file_name in modules_in_package:
        module_name = package_name + "." + file_name[:-3] # i.e. strip the last 3 characters '.py'
        rs.set_metadata(Resource.KEY_URI_PACKAGE_MODULENAME, module_name)
        rs.set_metadata(Resource.KEY_URI_DATASTORE_RESOURCE_TYPE, Resource.KEY_URI_PACKAGE_TYPE_MODULE)
                                         
    return rs

if __name__ == '__main__':
    
    # FIXME: remove the need for a specific current directory . 
    parser = OptionParser(usage=u"%s options. (Note: must be in the build.py source directory.)" % sys.argv[0])

    parser.add_option("-t", "--test",
    dest=u"test", action="store_true", default=True,
    help=u"Test Build to a local directory. Does NOT upload to Google Code.")

    parser.add_option("-b", "--builddirectory",
    dest=u"build_directory",
    help=u"Absolute Path to the directory which to build from.")
    
    parser.add_option("-d", "--distributiondirectory",
    dest=u"distribution_directory",
    help=u"Absolute Path to the directory which the package will be built to.")    

    parser.add_option("-u", "--username",
    dest=u"username", 
    help=u"Google Code username")
    
    parser.add_option("--pass", "--password",
    dest=u"password", 
    help=u"Google Code password")

    parser.add_option("-p", "--package", "--package-path",
    dest=u"package_path", 
    help=u"Path to the package to build. e.g base/datastore")

    parser.add_option("-v", "--version",
    dest=u"version", 
    help=u"Version of the package to build. e.g. X.X.X")

    (options, args) = parser.parse_args()
    options.script = os.path.split(sys.argv[0])[1]
    
    if options.test is False:      
        import pysvn  
        summary = raw_input('Enter a short summary for the build.\n').strip()
        labels = raw_input('Enter label(s) for the build. These label(s) should be comma seperated.\n').strip().split(',')
        print("Build Summary\nPackage: {0}\nVersion: {1}\nSummary: {2}\nLabels: {3}".format(options.package_path, options.version, summary, labels)) 

        response = raw_input('Proceed with build? (y/n)\n').strip().lower()  
        if response != 'y':
            sys.exit()

    if not options.build_directory:
        print ("No build directory specified.")
        sys.exit()
    else:    
        build_dir = options.build_directory
        
    if options.test is False:
        # check out yarra projects to the build folder
        print('Checking out code from repository')
        client = pysvn.Client()
        client.checkout('http://yarra.googlecode.com/svn/trunk', build_dir)

    if not options.distribution_directory:
        print ("No distribution directory specified.")
        sys.exit()
    else:
        # make a temp dir to store the zip to upload
        dist_path = options.distribution_directory
    
    project, sep, package = options.package_path.partition("/")
    
    if options.version is None:
        print("No version specified.")
        sys.exit()
        
    # create the path to the zip
    zip_file_name = package + "_v" + str(options.version) + ".yarra"
    zip_file_path = os.path.join(dist_path, zip_file_name)
    
    # if the file_name exists already then remove it
    if os.path.exists(zip_file_path):
        os.remove(zip_file_path)
    
    # create the zip file_name
    zip_file = ZipFile(zip_file_path, "w")
    
    manifest_file_path = os.path.join(build_dir, project, 'src', package, CONFIGURATION_XML)
    manifest_file = None
    try:
        manifest_file = open(manifest_file_path, 'r')
        manifest = Resource(manifest_file.read())
    except IOError:
        print 'Configuration file not found in package'
        sys.exit(1)        
    except DeserialisationException:
        print 'Configuration file is not a valid deserialised resource'
        sys.exit(1)        
    finally:
        if manifest_file:
            manifest_file.close()
        manifest_file = None
    
    # determine which modules to package
    modules_in_package = get_modules(manifest)
        
    print('Adding files to {0}'.format(zip_file_name))
    try:
        # search the build directory. root is the path to the current dir. dirs is a list of directories in the current dir. 
        # files is a list of files in the current dir
        for root, dirs, files in os.walk(build_dir):
            # if the root is path of the package we want to build
            if root == os.path.join(build_dir, project, 'src', package):
                # if there are any files
                if files:
                    # go through all the files
                    for file_name in files:
                        # as long as they aren't the compiled python modules
                        # TODO: include __init__.py eventually. Find out why it is not currently working
                        if not file_name.endswith('.pyc') and not file_name.startswith('.'):
                            # read the contents of it as a string
                            file = open(os.path.join(root, file_name), 'r')
                            content = file.read()
                            # make sure that we consistently use \n as the line ending.
                            content = content.replace('\r\n', '\n')
                            file.close()
                            file = None

                            # try to create a resource
                            rs = create_resource_from_file(file_name, content, package, modules_in_package)
                            
                            if rs:
                                if file_name.endswith(XML_FILE_EXTENSION):
                                    xml_file_name = file_name
                                else:
                                    xml_file_name = file_name + ".xml"
                                # write it to the zip
                                zip_file.writestr(xml_file_name, rs.serialise())
                                
                                print('Added {0}'.format(xml_file_name))
    finally:    
        # close the file
        zip_file.close()
        zip_file = None
        
        #Calculate the package hash.
        #SHA1 is used to be the same as Google Code
        file = open(zip_file_path, 'rb')
        content = file.read()
        file.close()
        file = None
        hash = hashlib.sha1(content).hexdigest()
        print("Finished creating package: %s (SHA1:%s), %s bytes." %(package, hash, len(content)))
    
    if options.test is False:    
        print('Uploading to google')
        # upload to google
        http_code, http_reason, file_url = googlecode_upload.upload(zip_file_path, 'yarra', options.username, options.password, summary, labels)
        
        if http_code == 201:
            print('Build uploaded successfully.')
        else:
            print('Build Failed. Reason: {0}'.format(http_reason)) 